<?php

set_time_limit(0);

/**
 * Реализация транспортного уровня модели OSI
 * Врядли для большинства приложений здесь что-то нужно будет менять. Так что лучше не трогать.
 * @author Alexander NeonXP Kiryukhin
 * @copyright 2011 Alexander NeonXP Kiryukhin
 * @license GPLv2
 */

class server {
    private $sckSocket,$bInnerLoop, $arClients, $arRead, $arTimes = array(), $iTimeoutTime = 0, $sBuffer, $callback, $arIP;
    function __construct($arParams) {
        $this->callback = isset($arParams['request']) ? $arParams['request'] : '';
        $this->bInnerLoop = isset($arParams['single']) ? $arParams['single'] : true;
        if (isset($arParams['timeout'])) {
            $this->iTimeoutTime = $arParams['timeout'];
        } else {
            $this->iTimeoutTime = 0;
        }

    }
    function listen($sIp='127.0.0.1',$iPort=4000) {
        $this->socketConnect($sIp, $iPort);

        log::raise("Started server on http://$sIp:$iPort");
        if ($this->bInnerLoop) {
            while (true) {
                $this->tick();
            }
        }
    }
    function tick() {
        timer::getInstance()->tick();
        $this->arRead = $this->arClients;
        $callback = $this->callback;
        if (count($this->arTimes) > 0)
            if ($this->iTimeoutTime > 0) {
                foreach ($this->arTimes as $iIndex => $iTime) {
                    if ((time() - $iTime) > $this->iTimeoutTime) {
                        socket_close($this->arRead[$iIndex + 1]);
                        $iKey = array_search($this->arRead[$iIndex + 1], $this->arClients);
                        unset($this->arClients[$iKey]);
                        unset($this->arIP[$iKey - 1]);
                        unset($this->arTimes[$iIndex]);
                        continue;
                    }
                }
            }
            $write=null;
            $except=null;
        if (socket_select($this->arRead, $write, $except, 1) < 1)
            return;
        if (in_array($this->sckSocket, $this->arRead)) {
            $this->arClients[] = $sckNewSocket = socket_accept($this->sckSocket);
            socket_getpeername($sckNewSocket, $ip);
            $iKey = array_search($this->sckSocket, $this->arRead);
            $this->arIP[] = $ip;
            $this->arTimes[] = time();
            unset($this->arRead[$iKey]);
            $this->arRead[] = $sckNewSocket;
            return;
        }
        foreach ($this->arRead as $index => $sckReadSocket) {
            $this->sBuffer = socket_read($sckReadSocket, 1024);
            if ($this->sBuffer === false) {
                $iKey = array_search($sckReadSocket, $this->arClients);
                unset($this->arClients[$iKey]);
                unset($this->arIP[$iKey - 1]);
                unset($this->arTimes[$iKey - 1]);
                continue;
            }
            $this->sBuffer = trim($this->sBuffer);
            if (!empty($this->sBuffer)) {
                $this->arTimes[$index - 1] = time();
                $callback(new http($this->sBuffer, $sckReadSocket));
                socket_close($sckReadSocket);
                $key = array_search($sckReadSocket, $this->arClients);
                unset($this->arClients[$key]);
                unset($this->arIP[$key - 1]);
                unset($this->arTimes[$key - 1]);
            }
        }
    }

    function __destruct() {
        log::raise("Server stoped");
        socket_close($this->sckSocket);
    }

    private function socketConnect($sIp, $iPort) {
        $this->sckSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_bind($this->sckSocket, $sIp, $iPort);
        socket_set_option($this->sckSocket, SOL_SOCKET, SO_KEEPALIVE, 1);
        socket_set_nonblock($this->sckSocket);
        socket_listen($this->sckSocket, 1000000);
        $this->arClients = array($this->sckSocket);
    }


}

